using System.Diagnostics;

namespace rsync
{
    /// <summary>
    /// 终端的输入接口，保持着多个帧数的输入缓存
    /// </summary>
    internal class Input
    {
        /// <summary>
        /// 保存的输入数量帧数
        /// </summary>
        public const int QueueSize = 128;
        
        /// <summary>
        /// 帧延迟量
        /// </summary>
        private uint _frameDelay;
        
        /// <summary>
        ///输入的数据缓存 
        /// </summary>
        private InputData[] _inputs;
        
        /// <summary>
        ///输入的预测缓存
        /// </summary>
        private InputData[] _lastPredictedInputs;

        /// <summary>
        /// 构建一个帧输入缓存对象，用于保存输入数据
        /// </summary>
        /// <param name="inputSize">玩家帧输入的大小</param>
        /// <param name="playerCount">玩家数量</param>
        /// <param name="frameDelay">帧延迟量</param>
        public Input(uint inputSize, uint playerCount, uint frameDelay = 0)
        {
            _frameDelay = frameDelay;
            _inputs = new InputData[QueueSize];
            _lastPredictedInputs = new InputData[QueueSize];

            for (int i = 0; i < _inputs.Length; i++)
            {
                _inputs[i] = new InputData(InputData.NullFrame, inputSize, playerCount);
                _lastPredictedInputs[i] = new InputData(InputData.NullFrame, inputSize, playerCount);
            }
        }

        /// <summary>
        /// 获取预测缓存的输入
        /// </summary>
        /// <param name="frame"></param>
        /// <returns></returns>
        public InputData GetPredictedInput(int frame)
        {
            return _lastPredictedInputs[frame % QueueSize];
        }

        /// <summary>
        /// 返回帧延迟量
        /// </summary>
        /// <returns></returns>
        public uint GetFrameDelay() => _frameDelay;

        /// <summary>
        /// 添加一个帧输入到缓存
        /// </summary>
        /// <param name="frame">帧ID</param>
        /// <param name="inputData">输入数据</param>
        public void AddInput(int frame, InputData inputData)
        {
            Debug.Assert(frame >= 0);

            frame += (int)_frameDelay;
            _inputs[frame % QueueSize] = new InputData(inputData);
            _inputs[frame % QueueSize].Frame = frame;
        }

        /// <summary>
        /// 获取一个帧输入，如果需要预测，则根据上一个输入返回一个预测输入
        /// </summary>
        /// <param name="frame">帧ID</param>
        /// <param name="predict">是否是预测输入</param>
        /// <returns></returns>
        public InputData GetInput(int frame, bool predict = true)
        {
            Debug.Assert(frame >= 0);

            int frameOffset = frame % QueueSize;
            //预测
            if (predict)
            {
                //如果帧是NullFrame或帧不匹配，根据前一帧预测下一帧。
                if (_inputs[frameOffset].Frame == InputData.NullFrame ||
                    _inputs[frameOffset].Frame != frame)
                {
                    //根据前一帧预测当前帧。
                    var prevFrame = _inputs[PreviousFrame(frameOffset)];
                    _inputs[frameOffset] = new InputData(prevFrame);
                    _inputs[frameOffset].Frame = InputData.NullFrame;

                    // 向队列中添加新的预测帧。当后来被证明输入是正确的或错误的，它将被重置。
                    _lastPredictedInputs[frameOffset] = new InputData(_inputs[frameOffset]);
                    _lastPredictedInputs[frameOffset].Frame = frame;
                }
            }
            return new InputData(_inputs[frameOffset]);
        }

        /// <summary>
        /// 重置预测帧输入为空，即没有任何操作
        /// </summary>
        /// <param name="frame">帧ID</param>
        public void ResetPrediction(int frame)
        {
            //当重置预测时，我们只是将帧设为空帧。
            int frameOffset = frame % QueueSize;
            _lastPredictedInputs[frameOffset].Frame = InputData.NullFrame;
        }

        /// <summary>
        /// offset的上一帧ID
        /// </summary>
        /// <param name="offset">帧ID</param>
        /// <returns></returns>
        private int PreviousFrame(int offset) => (((offset) == 0) ? (QueueSize - 1) : ((offset) - 1));
    }
}